package com.sgcc.nfc.lock.core.location

import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt

/**
 * Utilities for converting between coordinate systems.
 *
 * AMap uses GCJ-02 internally. Android's [android.location.LocationManager] returns WGS-84
 * coordinates, which introduces a visible offset when plotted on AMap tiles. The helper below
 * converts WGS-84 to GCJ-02 when the point lies inside mainland China.
 */
object CoordinateConverter {
    private const val A = 6378245.0
    private const val EE = 0.00669342162296594323
    private const val X_PI = Math.PI * 3000.0 / 180.0

    fun wgs84ToGcj02(latitude: Double, longitude: Double): Pair<Double, Double> {
        if (isOutsideChina(latitude, longitude)) {
            return latitude to longitude
        }
        var dLat = transformLat(longitude - 105.0, latitude - 35.0)
        var dLon = transformLon(longitude - 105.0, latitude - 35.0)
        val radLat = latitude / 180.0 * Math.PI
        var magic = sin(radLat)
        magic = 1 - EE * magic * magic
        val sqrtMagic = sqrt(magic)
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI)
        dLon = (dLon * 180.0) / (A / sqrtMagic * cos(radLat) * Math.PI)
        val mgLat = latitude + dLat
        val mgLon = longitude + dLon
        return mgLat to mgLon
    }

    private fun isOutsideChina(lat: Double, lon: Double): Boolean {
        if (lon < 72.004 || lon > 137.8347) return true
        if (lat < 0.8293 || lat > 55.8271) return true
        return false
    }

    private fun transformLat(x: Double, y: Double): Double {
        var result = -100.0 + 2.0 * x + 3.0 * y
        result += 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(kotlin.math.abs(x))
        result += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
        result += (20.0 * sin(y * Math.PI) + 40.0 * sin(y / 3.0 * Math.PI)) * 2.0 / 3.0
        result += (160.0 * sin(y / 12.0 * Math.PI) + 320 * sin(y * Math.PI / 30.0)) * 2.0 / 3.0
        return result
    }

    private fun transformLon(x: Double, y: Double): Double {
        var result = 300.0 + x + 2.0 * y
        result += 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(kotlin.math.abs(x))
        result += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
        result += (20.0 * sin(x * Math.PI) + 40.0 * sin(x / 3.0 * Math.PI)) * 2.0 / 3.0
        result += (150.0 * sin(x / 12.0 * Math.PI) + 300.0 * sin(x / 30.0 * Math.PI)) * 2.0 / 3.0
        return result
    }

    fun gcj02ToBd09(latitude: Double, longitude: Double): Pair<Double, Double> {
        val z = sqrt(longitude * longitude + latitude * latitude) + 0.00002 * sin(latitude * X_PI)
        val theta = atan2(latitude, longitude) + 0.000003 * cos(longitude * X_PI)
        val bdLon = z * cos(theta) + 0.0065
        val bdLat = z * sin(theta) + 0.006
        return bdLat to bdLon
    }
}
